#include "qtch/email/smtp.h"
#include "qtch/log.h"

namespace qtch {
static qtch::Logger::ptr logger = QTCH_LOG_NAME("system");

SmtpClient::SmtpClient(Socket::ptr sock)
    :qtch::SocketStream(sock) {
}

SmtpClient::ptr SmtpClient::Create(const std::string& host, uint32_t port, bool ssl){

    qtch::IPAddress::ptr addr = qtch::Address::LookupAnyIPAdress(host);
    if(!addr) {
        QTCH_LOG_ERROR(logger) << "invalid smtp server: " << host << ":" << port 
            << " ssl=" << ssl;
        return nullptr;
    }
    addr->setPort(port);
    Socket::ptr sock;
    if(ssl){
        sock = qtch::SSLSocket::CreateTCP(addr);
    }else{
        sock = qtch::Socket::CreateTCP(addr);
    }
    if(!sock->connect(addr)){
        QTCH_LOG_ERROR(logger) << "connect smtp server: " << host << ":" << port
            << " ssl=" << ssl;
        return nullptr;
    }
    std::string buf;
    buf.resize(1024);

    SmtpClient::ptr rt(new SmtpClient(sock));
    int len = rt->read(&buf[0],buf.size());
    if(len<=0){
        return nullptr;
    }

    buf.resize(len);
    if(qtch::TypeUtil::Atoi(buf.c_str()) != 220){
        return nullptr;
    }
    rt->m_host = host;
    return rt;

}

SmtpResult::ptr SmtpClient::doCmd(const std::string& cmd, bool debug){
    if(writeFixSize(cmd.c_str(),cmd.size())<=0){
        return std::make_shared<SmtpResult>(SmtpResult::Result::IO_ERROR,"write io error");
    }
    std::string buf;
    buf.resize(4096);
    int len = read(&buf[0],buf.size());
    if(len <= 0){
        return std::make_shared<SmtpResult>(SmtpResult::Result::IO_ERROR,"read io error");
    }
    buf.resize(len);
    if(debug){
        m_ss << "C: " << cmd << "\n";
        m_ss << "S: " << buf << "\n";
    }
    size_t pos = buf.find(" -");
    std::string num = (pos == std::string::npos) ? buf : buf.substr(0,pos);
    int code = qtch::TypeUtil::Atoi(num);
    if(code >= 400){
        return std::make_shared<SmtpResult>(SmtpResult::Result::IO_ERROR,buf);
    }

    return nullptr;
}

SmtpResult::ptr SmtpClient::send(EMail::ptr email, int64_t timeout_ms, bool debug){
#define DO_CMD() \
    result = doCmd(cmd, debug); \
    if(result) { \
        return result; \
    }


    Socket::ptr sock = getSocket();
    if(sock){
        sock->setRecvTimeout(timeout_ms);
        sock->setSendTimeout(timeout_ms);
    }

    SmtpResult::ptr result;
    std::string cmd = "HELO " + m_host + "\r\n";
    DO_CMD();
    if(!m_authed && !email->getFromEMailAddress().empty()){
        cmd = "AUTH LOGIN\r\n";
        DO_CMD();
        // auto pos = email->getFromEMailAddress().find('@');
        cmd = qtch::CypherUtil::encodeBase64(email->getFromEMailAddress()) + "\r\n";
        DO_CMD();
        cmd = qtch::CypherUtil::encodeBase64(email->getFromEMailPasswd()) + "\r\n";
        DO_CMD();
        m_authed = true;
    }
    cmd = "MAIL FROM: <" + email->getFromEMailAddress() + ">\r\n";
    DO_CMD();
    std::set<std::string> targets;

#define XX(fun) \
    for(auto& i : email->fun()) { \
        targets.insert(i); \
    }
    XX(getToEMailAddress);
    XX(getCcEMailAddress);
    XX(getBccEMailAddress);
#undef XX

    for(auto& i : targets){
        cmd = "RCPT TO: <" + i + ">\r\n";
        DO_CMD();
    }

    cmd = "DATA\r\n";
    DO_CMD();
    
    auto& entitys = email->getEntitys();

    std::stringstream ss;
    ss << "SUBJECT: " << email->getTitle() << "\r\n";
    ss << "From: <" << email->getFromEMailAddress() << ">\r\n";
    ss << "To: ";
#define XX(fun) \
    do{ \
        auto& v = email->fun(); \
        for(size_t i = 0; i < v.size(); ++i){ \
            if(i){ \
                ss << ","; \
            } \
            ss << "<" << v[i] << ">"; \
        } \
        if(!v.empty()){ \
            ss << "\r\n"; \
        } \
    } while(0);

    XX(getToEMailAddress);

    if(!email->getCcEMailAddress().empty()){
        ss << "Cc: ";
        XX(getCcEMailAddress);
    }

    std::string boundary;
    if(!entitys.empty()){
        boundary = qtch::StringUtil::rand_string(16);
        ss << "Content-Type: multipart/mixed;boundary="
           << boundary << "\r\n";
    }
    ss << "MIME-Version: 1.0\r\n";
    if(!boundary.empty()){
        ss << "\r\n--" << boundary << "\r\n";
    }

    ss << "Content-Type: text/html;charset=\"utf-8\"\r\n"
       << "\r\n"
       << email->getBody() << "\r\n";
    for(auto& i : entitys){
        ss << "\r\n--" << boundary << "\r\n";
        ss << i->toString();
    }
    if(!boundary.empty()){
        ss << "\r\n--" << boundary << "--\r\n";
    }
    ss << "\r\n.\r\n";
    cmd = ss.str();
    DO_CMD();

#undef XX
#undef DO_CMD

    return std::make_shared<SmtpResult>(SmtpResult::Result::OK,"ok");
}

std::string SmtpClient::getDebugInfo(){
    return m_ss.str();
}

}

